#Anaconda/envs/pyqtenv python
# -*- coding: utf-8 -*-
'''
SfcdSagin.py
============

.. module:: SfcdSagin
  :platform: Windows, Linux
  :synopsis: SFC-D 算法实验的 SAGIN 仿真场景脚本

.. moduleauthor:: WangXi

简介
----

该模块实现了对SFC-D类型算法的SAGIN仿真场景的仿真脚本的功能, 主要用于使用NetOrchestr框架进行SFC-D算法的仿真实验设计。它提供了以下特性:

- 构建了包含有卫星节点、无人机节点、地面基站节点的通信基础设施
- 设置各个基础设施搭载计算平台支持部署微服务应用
- 构建了包含有用户终端节点的服务发起请求场景
- 构建了包含有服务链路的服务链路请求场景
- 构建了包含有控制器节点的服务链编排模型 MANO
- 所构建的场景中各个通信基础设施与用户终端的通信能力如下

               --------   1000 km
            ---| Sat  |-------------
            |  --------            |
            |     |                |
            |     |1000 km         |
            |     |                |
            |  --------   5 km     |
    1000 km |  | Uav  |-------------
            |  --------            |
            |     |                |
            |     |5 km            |
            |     |                |
            |  --------  10 km  --------
            ---|Ground|---------|  Ue  |      
               --------         --------

版本
----

- 版本 1.0 (2025/10/27): 初始版本

'''


import os
import sys
import random
import argparse
import numpy as np
from astropy import units as u
from astropy.time import Time
from netorchestr.common.util import str2bool
from netorchestr.envir.node.controller.mano.solver_deploy import SOLVER_HELP_INFO

parser = argparse.ArgumentParser(description='SfcdSagin 运行指令解析参数示例')

env_arg = parser.add_argument_group('运行环境参数')
env_arg.add_argument("--seed", type=int, default=0, 
                     help='随机数种子, 默认0')
env_arg.add_argument("--id", type=str, default="", 
                     help="仿真场景ID, 默认为网络名称+时间戳")
env_arg.add_argument("--req_rate", type=float, default=1.0, 
                     help='服务功能链请求速率, 默认1.0, 单位为每秒')
env_arg.add_argument("--res_factor", type=float, default=1.0,  
                     help='基底网络资源缩放因子, 默认为1.0')
env_arg.add_argument("--solver", type=str, default="SolverDeploySharedRandom",
                     help=SOLVER_HELP_INFO)
env_arg.add_argument("--work_mode", type=str, default="train", choices=['train', 'validate'], 
                     help='智能算法类型的服务功能链编排求解器工作模式设置, 默认为train')

# 可以通过设置随机数种子使得生成的随机场景相同

random.seed(parser.parse_args().seed)   # Python的随机性
os.environ['PYTHONHASHSEED'] = str(parser.parse_args().seed)    # 设置Python哈希种子，为了禁止hash随机化，使得实验可复现
np.random.seed(parser.parse_args().seed)   # numpy的随机性

from netorchestr.envir.base import ONet

net = ONet('SfcdSagin',
           sim_id=parser.parse_args().id,
           seed_id=0,
           work_dir=os.path.dirname(__file__))

init_time = Time("2021-07-22 00:00:00")

LANDMARK_LOCATIONS = {
    '河南省郑州市中原区须水镇': [113.51,34.76, 0.0],
    '河南省郑州市荥阳市王村镇': [113.28,34.88, 0.0],
    '河南省郑州市巩义市小关镇': [113.18,34.71, 0.0],
    '河南省郑州市巩义市米河镇上空': [113.30,34.80, 4.0],
    '河南省郑州市荥阳市崔庙镇上空': [113.31,34.75, 4.0],
    '河南省郑州市荥阳市乔楼镇上空': [113.39,34.77, 4.0],
    '河南省郑州市荥阳市广武镇上空': [113.40,34.85, 4.0],
    '河南省郑州市中原区沟赵乡上空': [113.50,34.81, 4.0],
    '贵州安顺黄果树机场': [105.883722, 26.262808, 0.0],
    '北京大兴国际机场': [116.409275, 39.51952, 0.0],
    }

LANDMARK_LOCATIONS_FLOOD = {
    key: value for key, value in LANDMARK_LOCATIONS.items() 
    if "机场" not in key
}

# region step1: 基底网络设施 --------------------------------------------

from netorchestr.envir.mobility import Trajectory
from netorchestr.envir.node.ue import UeWithSfcReq
from netorchestr.envir.node.satellite import ConstellationServerPlatform
from netorchestr.envir.node.ground import GroundServerPlatform
from netorchestr.envir.node.uav import UavServerPlatform
from netorchestr.envir.mobility import Trajectory
from netorchestr.envir.physicallayer import RadioMedium

sat_num = 40
plane_num = 4
constellation = ConstellationServerPlatform(name='Ku', 
                                            init_time=init_time, 
                                            sat_num=sat_num, 
                                            plane_num=plane_num, 
                                            ip_pool_str_list=[f"192.168.{i}.0/24" for i in range(sat_num)], 
                                            node_resource_dict={'cpu':parser.parse_args().res_factor*4, 
                                                                'ram':parser.parse_args().res_factor*8*u.GB, 
                                                                'rom':parser.parse_args().res_factor*64*u.GB},
                                            link_resource_dict={'band':100*u.Mbit/u.s})
for sat in constellation.satellites.values():
    net.add_module(sat)
    
station_0 = GroundServerPlatform(name='Ground0', 
                                 init_time=init_time, 
                                 init_gps=LANDMARK_LOCATIONS['河南省郑州市中原区须水镇'],
                                 ip_pool_str='192.168.40.0/24',
                                 node_resource_dict={'cpu':parser.parse_args().res_factor*16, 
                                                     'ram':parser.parse_args().res_factor*64*u.GB, 
                                                     'rom':parser.parse_args().res_factor*2*u.TB},
                                 link_resource_dict={'band':100*u.Mbit/u.s})
net.add_module(station_0)

station_1 = GroundServerPlatform(name='Ground1', 
                                 init_time=init_time, 
                                 init_gps=LANDMARK_LOCATIONS['河南省郑州市荥阳市王村镇'],
                                 ip_pool_str='192.168.41.0/24',
                                 node_resource_dict={'cpu':parser.parse_args().res_factor*16, 
                                                     'ram':parser.parse_args().res_factor*64*u.GB, 
                                                     'rom':parser.parse_args().res_factor*2*u.TB},
                                 link_resource_dict={'band':100*u.Mbit/u.s})
net.add_module(station_1)

station_2 = GroundServerPlatform(name='Ground2', 
                                 init_time=init_time, 
                                 init_gps=LANDMARK_LOCATIONS['河南省郑州市巩义市小关镇'],
                                 ip_pool_str='192.168.42.0/24',
                                 node_resource_dict={'cpu':parser.parse_args().res_factor*16, 
                                                     'ram':parser.parse_args().res_factor*64*u.GB, 
                                                     'rom':parser.parse_args().res_factor*2*u.TB},
                                 link_resource_dict={'band':100*u.Mbit/u.s})
net.add_module(station_2)

uav_0 = UavServerPlatform(name='Uav0', 
                          init_time=init_time, 
                          init_gps=LANDMARK_LOCATIONS['贵州安顺黄果树机场'],
                          ip_pool_str='192.168.43.0/24',
                          node_resource_dict={'cpu':parser.parse_args().res_factor*8, 
                                              'ram':parser.parse_args().res_factor*16*u.GB, 
                                              'rom':parser.parse_args().res_factor*256*u.GB},
                          link_resource_dict={'band':100*u.Mbit/u.s})
uav_0_trajectory = Trajectory(name='U0Tr', avg_speed=(200*u.km/u.h).to(u.m/u.s).value)
uav_0_trajectory.add_straight(start=LANDMARK_LOCATIONS['贵州安顺黄果树机场'], end=LANDMARK_LOCATIONS['河南省郑州市巩义市米河镇上空'])
uav_0_trajectory.add_circle(center=LANDMARK_LOCATIONS['河南省郑州市巩义市米河镇上空'], radius=5000, loops=50)
uav_0_trajectory.add_straight(start=LANDMARK_LOCATIONS['河南省郑州市巩义市米河镇上空'], end=LANDMARK_LOCATIONS['贵州安顺黄果树机场'])
uav_0.mobiusTraj.set_trajectory(uav_0_trajectory, init_time)
net.add_module(uav_0)

uav_1 = UavServerPlatform(name='Uav1', 
                          init_time=init_time, 
                          init_gps=LANDMARK_LOCATIONS['贵州安顺黄果树机场'],
                          ip_pool_str='192.168.44.0/24',
                          node_resource_dict={'cpu':parser.parse_args().res_factor*8, 
                                              'ram':parser.parse_args().res_factor*16*u.GB, 
                                              'rom':parser.parse_args().res_factor*256*u.GB},
                          link_resource_dict={'band':100*u.Mbit/u.s})
uav_1_trajectory = Trajectory(name='U1Tr', avg_speed=(200*u.km/u.h).to(u.m/u.s).value)
uav_1_trajectory.add_straight(start=LANDMARK_LOCATIONS['贵州安顺黄果树机场'], end=LANDMARK_LOCATIONS['河南省郑州市荥阳市崔庙镇上空'])
uav_1_trajectory.add_circle(center=LANDMARK_LOCATIONS['河南省郑州市荥阳市崔庙镇上空'], radius=5000, loops=50)
uav_1_trajectory.add_straight(start=LANDMARK_LOCATIONS['河南省郑州市荥阳市崔庙镇上空'], end=LANDMARK_LOCATIONS['贵州安顺黄果树机场'])
uav_1.mobiusTraj.set_trajectory(uav_1_trajectory, init_time)
net.add_module(uav_1)

uav_2 = UavServerPlatform(name='Uav2', 
                          init_time=init_time, 
                          init_gps=LANDMARK_LOCATIONS['北京大兴国际机场'],
                          ip_pool_str='192.168.45.0/24',
                          node_resource_dict={'cpu':parser.parse_args().res_factor*8, 
                                              'ram':parser.parse_args().res_factor*16*u.GB, 
                                              'rom':parser.parse_args().res_factor*256*u.GB},
                          link_resource_dict={'band':100*u.Mbit/u.s})
uav_2_trajectory = Trajectory(name='U2Tr', avg_speed=(150*u.km/u.h).to(u.m/u.s).value)
uav_2_trajectory.add_straight(start=LANDMARK_LOCATIONS['北京大兴国际机场'], end=LANDMARK_LOCATIONS['河南省郑州市荥阳市乔楼镇上空'])
uav_2_trajectory.add_circle(center=LANDMARK_LOCATIONS['河南省郑州市荥阳市乔楼镇上空'], radius=5000, loops=50)
uav_2_trajectory.add_straight(start=LANDMARK_LOCATIONS['河南省郑州市荥阳市乔楼镇上空'], end=LANDMARK_LOCATIONS['北京大兴国际机场'])
uav_2.mobiusTraj.set_trajectory(uav_2_trajectory, init_time)
net.add_module(uav_2)

uav_3 = UavServerPlatform(name='Uav3', 
                          init_time=init_time, 
                          init_gps=LANDMARK_LOCATIONS['北京大兴国际机场'],
                          ip_pool_str='192.168.46.0/24',
                          node_resource_dict={'cpu':parser.parse_args().res_factor*8, 
                                              'ram':parser.parse_args().res_factor*16*u.GB, 
                                              'rom':parser.parse_args().res_factor*256*u.GB},
                          link_resource_dict={'band':100*u.Mbit/u.s})
uav_3_trajectory = Trajectory(name='U3Tr', avg_speed=(150*u.km/u.h).to(u.m/u.s).value)
uav_3_trajectory.add_straight(start=LANDMARK_LOCATIONS['北京大兴国际机场'], end=LANDMARK_LOCATIONS['河南省郑州市荥阳市广武镇上空'])
uav_3_trajectory.add_circle(center=LANDMARK_LOCATIONS['河南省郑州市荥阳市广武镇上空'], radius=5000, loops=50)
uav_3_trajectory.add_straight(start=LANDMARK_LOCATIONS['河南省郑州市荥阳市广武镇上空'], end=LANDMARK_LOCATIONS['北京大兴国际机场'])
uav_3.mobiusTraj.set_trajectory(uav_3_trajectory, init_time)
net.add_module(uav_3)

uav_4 = UavServerPlatform(name='Uav4', 
                          init_time=init_time, 
                          init_gps=LANDMARK_LOCATIONS['北京大兴国际机场'],
                          ip_pool_str='192.168.47.0/24',
                          node_resource_dict={'cpu':parser.parse_args().res_factor*8, 
                                              'ram':parser.parse_args().res_factor*16*u.GB, 
                                              'rom':parser.parse_args().res_factor*256*u.GB},
                          link_resource_dict={'band':100*u.Mbit/u.s})
uav_4_trajectory = Trajectory(name='U4Tr', avg_speed=(150*u.km/u.h).to(u.m/u.s).value)
uav_4_trajectory.add_straight(start=LANDMARK_LOCATIONS['北京大兴国际机场'], end=LANDMARK_LOCATIONS['河南省郑州市中原区沟赵乡上空'])
uav_4_trajectory.add_circle(center=LANDMARK_LOCATIONS['河南省郑州市中原区沟赵乡上空'], radius=5000, loops=50)
uav_4_trajectory.add_straight(start=LANDMARK_LOCATIONS['河南省郑州市中原区沟赵乡上空'], end=LANDMARK_LOCATIONS['北京大兴国际机场'])
uav_4.mobiusTraj.set_trajectory(uav_4_trajectory, init_time)
net.add_module(uav_4)


# region step2: 服务发起用户 ---------------------------------------------------

from netorchestr.envir.mobility import MobilityBase

sfc_num = 300
ue_num = sfc_num * 2
ue_gpslist = MobilityBase.get_random_gpslist_in_rectangular_area(
                area=MobilityBase.get_rectangular_area(LANDMARK_LOCATIONS_FLOOD),
                num=ue_num)
ue_list = []
for ue_index in range(ue_num):
    ue_name = f'Ue{ue_index}'
    ue_init_gps = ue_gpslist[ue_index]
    ue = UeWithSfcReq(name=ue_name, 
                     init_time=init_time, 
                     init_gps=ue_init_gps)
    # 如果不规划航迹则为静态运动模型
    # ue_trajectory = Trajectory(name=f'{ue_name}Tr', avg_speed=1.5)
    # ue_trajectory.add_random_in_circle(center=ue_init_gps, radius=1000.0, anchor_number=100)
    # ue.mobiusTraj.set_trajectory(ue_trajectory, init_time)
    ue_list.append(ue)
    net.add_module(ue)

radio_medium = RadioMedium('RadioMedium')
net.add_module(radio_medium)
net.ready_for_medium(radio_medium)

# endregion

# region step3: 微服务部署模板 --------------------------------------------------

from netorchestr.envir.node.controller.mano.vnfm import VnfManager,VnfEm
from netorchestr.common.util import NumberGen

vnfManager = VnfManager()

vnf_type_num = 15
vnf_type_list = [f'type{i}' for i in range(vnf_type_num)]
vnf_req_cpu = NumberGen.getVector(vnf_type_num,**{'distribution':'uniform','dtype':'int','low':1,'high':4})
vnf_req_ram = NumberGen.getVector(vnf_type_num,**{'distribution':'uniform','dtype':'int','low':2,'high':8}) * u.GB
vnf_req_rom = NumberGen.getVector(vnf_type_num,**{'distribution':'uniform','dtype':'int','low':2,'high':16}) * u.GB

for i in range(vnf_type_num):
    vnfEm_template = VnfEm(type = vnf_type_list[i],
                           resource_limit = {'cpu':vnf_req_cpu[i], 
                                             'ram':vnf_req_ram[i], 
                                             'rom':vnf_req_rom[i]},
                           rate_per_core = 10,
                           cost_with_loc = {"Ground": 1,
                                            "Uav":1.1,
                                            "Sat":1.2}
                           )
    vnfManager.add_vnf_into_templates(vnfEm_template)
    
# endregion

# region step4: 用户服务请求 -------------------------------------------------

from netorchestr.envir.applications.ueapp import SfcReq
import random

sfc_start_time_group = np.cumsum(NumberGen.getVector(sfc_num,**{'distribution':'possion','dtype':'float','lam':parser.parse_args().req_rate,'reciprocal':True})) * u.min + 4 * u.hour
sfc_life_time_group = NumberGen.getVector(sfc_num,**{'distribution':'uniform','dtype':'float','low':5,'high':30}) * u.min
sfc_end_time_group = sfc_start_time_group + sfc_life_time_group
sfc_vnfs_num_group = NumberGen.getVector(sfc_num,**{'distribution':'uniform','dtype':'int','low':3,'high':15})
sfc_vnfs_type_group = {i:random.sample(vnf_type_list,sfc_vnfs_num_group[i]) for i in range(sfc_num)}

sfc_qos_URLLC_num = int(sfc_num*(2/8))
sfc_qos_mMTC_num = int(sfc_num*(5/8))
sfc_qos_eMBB_num = sfc_num-(sfc_qos_URLLC_num+sfc_qos_mMTC_num)
sfc_qos_level = ["URLLC" for i in range(sfc_qos_URLLC_num)]+["mMTC" for i in range(sfc_qos_mMTC_num)]+["eMBB" for i in range(sfc_qos_eMBB_num)]
np.random.shuffle(sfc_qos_level)
sfc_qos_URLLC_latency = NumberGen.getVector(sfc_qos_URLLC_num,**{'distribution':'uniform','dtype':'int','low':100,'high':200}) * u.ms
sfc_qos_mMTC_latency = NumberGen.getVector(sfc_qos_mMTC_num,**{'distribution':'uniform','dtype':'int','low':250,'high':350}) * u.ms
sfc_qos_eMBB_latency = NumberGen.getVector(sfc_qos_eMBB_num,**{'distribution':'uniform','dtype':'int','low':350,'high':450}) * u.ms

sfcReqlist = []
for i in range(sfc_num):
    random.shuffle(ue_list)
    if sfc_qos_level[i] == "URLLC":
        sfc_req = SfcReq(id=i,
                         start_time=sfc_start_time_group[i],
                         end_time=sfc_end_time_group[i],
                         sfc_type='UeAccess', 
                         sfc_vnfs_type=sfc_vnfs_type_group[i],
                         sfc_vnfs_shared=SfcReq.get_sfc_shared(sfc_qos_level[i],sfc_vnfs_num_group[i]),
                         sfc_qos={'level':sfc_qos_level[i],'latency':list(sfc_qos_URLLC_latency).pop(0),'overrate':0.001},
                         sfc_trans_model={'type':'Poisson',
                                          'interval':500*u.ms, 
                                          'payload_size':1*u.kbit},
                         sfc_end_point=[ue_list.pop(0),ue_list.pop(0)])
    elif sfc_qos_level[i] == "mMTC":
        sfc_req = SfcReq(id=i,
                         start_time=sfc_start_time_group[i],
                         end_time=sfc_end_time_group[i],
                         sfc_type='UeAccess', 
                         sfc_vnfs_type=sfc_vnfs_type_group[i],
                         sfc_vnfs_shared=SfcReq.get_sfc_shared(sfc_qos_level[i],sfc_vnfs_num_group[i]),
                         sfc_qos={'level':sfc_qos_level[i],'latency':list(sfc_qos_mMTC_latency).pop(0),'overrate':0.01},
                         sfc_trans_model={'type':'Poisson',
                                          'interval':500*u.ms, 
                                          'payload_size':1*u.kbit},
                         sfc_end_point=[ue_list.pop(0),ue_list.pop(0)]) 
    elif sfc_qos_level[i] == "eMBB":
        sfc_req = SfcReq(id=i,
                         start_time=sfc_start_time_group[i],
                         end_time=sfc_end_time_group[i],
                         sfc_type='UeAccess', 
                         sfc_vnfs_type=sfc_vnfs_type_group[i],
                         sfc_vnfs_shared=SfcReq.get_sfc_shared(sfc_qos_level[i],sfc_vnfs_num_group[i]),
                         sfc_qos={'level':sfc_qos_level[i],'latency':list(sfc_qos_eMBB_latency).pop(0),'overrate':0.1},
                         sfc_trans_model={'type':'Poisson',
                                          'interval':500*u.ms, 
                                          'payload_size':1*u.kbit},
                         sfc_end_point=[ue_list.pop(0),ue_list.pop(0)]) 

    sfcReqlist.append(sfc_req)

#  endregion

# region step5: 控制器及求解器 -----------------------------------------------------

from netorchestr.envir.node.controller.mano import ControllerMano
from netorchestr.envir.node.controller.mano.trace import TRACE_RESULT
from netorchestr.envir.node.controller.mano.solver_deploy import import_solver

solver_name = parser.parse_args().solver
solver_deploy = import_solver(solver_name)
solver_deploy.work_mode = parser.parse_args().work_mode

print(f"INFO: 设置 MANO 载入求解器 {solver_deploy.__name__}")
solver_deploy = solver_deploy(name=solver_deploy.__name__)

controller = ControllerMano(name='Controller', 
                            net=net, 
                            vnfManager=vnfManager, 
                            sfcReqlist=sfcReqlist)
controller.set_solver_deploy(solver_deploy)

if solver_deploy.name in ['SolverDeployGatDrl', 'SolverDeployDRLSFCP', 'SolverDeployPGARL', 'SolverDeployPPOSFC']:
    # 基于人工智能的算法可选在仿真开始前加载最新的模型参数
    solver_deploy.load_param(solver_deploy.get_newest_model_file(os.path.dirname(__file__)))
else:
    pass

net.add_module(controller)

# endregion

# region step6: 场景可视化检查
# （非必须步骤）

# net_draw_area = MobilityBase.get_rectangular_area(LANDMARK_LOCATIONS_FLOOD)
# net.get_area_gif(time_start=min(sfc_start_time_group),
#                  time_until=max(sfc_end_time_group)+1*u.min,
#                  time_accuracy=1 * u.min,
#                  draw_region=[net_draw_area['left_top'][0]-0.1,
#                               net_draw_area['right_bottom'][0]+0.1,
#                               net_draw_area['right_bottom'][1]-0.1,
#                               net_draw_area['left_top'][1]+0.1],
#                  filename=f"{net.name}_area.gif")
# net.get_area_gif(time_start=min(sfc_start_time_group),
#                  time_until=max(sfc_end_time_group)+1*u.min,
#                  time_accuracy=1 * u.min,
#                  draw_region=[-180,180,-90,90],
#                  filename=f"{net.name}_global.gif")

# endregion

# region step7: 开始仿真 --------------------------------------------------------

net.sim_time_accuracy = 1 * u.s
net.run(until = max(sfc_end_time_group)+1*u.min)

# endregion

# region step8: 仿真结果分析 --------------------------------------------------------

from netorchestr.common.util import DataAnalysis

DataAnalysis.getResult(TRACE_RESULT.filename)

# endregion

# region step9: 保存求解器参数 --------------------------------------------------------
# （非必须步骤）

solver_deploy.save_param()

# endregion
